package servlet;

import annotation.Controller;
import annotation.GetMapping;
import annotation.PostMapping;
import com.fasterxml.jackson.databind.ObjectMapper;
import engine.ViewEngine;
import result.ModelAndView;
import servlet.dispatcher.GetDispatcher;
import servlet.dispatcher.PostDispatcher;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;

@WebServlet(urlPatterns = "/*")
public class DispatcherServlet extends HttpServlet {
    private final Map<String, GetDispatcher> getMappings = new HashMap<>();
    private final Map<String, PostDispatcher> postMappings = new HashMap<>();

    private ViewEngine viewEngine;

    @Override
    public void init() {
        try {
            // 扫描控制器中的 Dispatcher
            scanControllerDispatcher();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException |
                 ClassNotFoundException e) {
            e.printStackTrace();
        }
        this.viewEngine = new ViewEngine(getServletContext());
    }

    private void scanControllerDispatcher() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        String packageName = "controller";
        // 获取包下的所有类
        List<Class<?>> classes = getClassesInPackage(packageName);

        // 反射创建实例
        for (Class<?> clazz : classes) {
            if (!clazz.isAnnotationPresent(Controller.class)) {
                continue;
            }
            Object instance = clazz.getDeclaredConstructor().newInstance();
            Method[] declaredMethods = clazz.getDeclaredMethods();
            ObjectMapper objectMapper = new ObjectMapper();
            for (Method method : declaredMethods) {
                if (method.isAnnotationPresent(GetMapping.class)) {
                    GetDispatcher dispatcher = new GetDispatcher();
                    GetMapping getMapping = method.getAnnotation(GetMapping.class);
                    String path = getMapping.value();
                    dispatcher.setMethod(method);
                    dispatcher.setInstance(instance);
                    dispatcher.setParameterClasses(method.getParameterTypes());
                    dispatcher.setParameterNames(Arrays.stream(method.getParameters()).map(Parameter::getName).toArray(String[]::new));
                    this.getMappings.put(path, dispatcher);
                } else if (method.isAnnotationPresent(PostMapping.class)) {
                    PostDispatcher dispatcher = new PostDispatcher();
                    PostMapping postMapping = method.getAnnotation(PostMapping.class);
                    String path = postMapping.value();
                    dispatcher.setMethod(method);
                    dispatcher.setInstance(instance);
                    dispatcher.setParameterClasses(method.getParameterTypes());
                    dispatcher.setObjectMapper(objectMapper);
                    this.postMappings.put(path, dispatcher);
                }
            }
        }
    }

    private List<Class<?>> getClassesInPackage(String packageName) throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();
        // 将包名转换为路径
        String path = packageName.replace('.', '/');
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL resource = classLoader.getResource(path);

        if (resource == null) {
            throw new IllegalArgumentException("Package not found: " + packageName);
        }

        File directory = new File(resource.getFile());
        if (directory.exists()) {
            // 扫描目录下的所有文件
            for (File file : directory.listFiles()) {
                if (file.isFile() && file.getName().endsWith(".class")) {
                    // 去掉文件后缀，获取类名
                    String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
                    // 加载类
                    Class<?> clazz = Class.forName(className);
                    classes.add(clazz);
                }
            }
        } else {
            throw new IllegalArgumentException("Directory not found: " + directory.getAbsolutePath());
        }

        return classes;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        String path = req.getRequestURI().substring(req.getContextPath().length());
        // 根据路径查找GetDispatcher:
        GetDispatcher dispatcher = this.getMappings.get(path);
        if (dispatcher == null) {
            // 未找到返回404:
            resp.sendError(404);
            return;
        }
        // 调用Controller方法获得返回值:
        ModelAndView modelAndView;
        try {
            modelAndView = dispatcher.invoke(req, resp);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        // 允许返回null:
        if (modelAndView == null) {
            return;
        }
        // 处理重定向
        String view = modelAndView.getView();
        if (view.startsWith("redirect:")) {
            resp.sendRedirect(req.getContextPath() + view.substring("redirect:".length()));
        } else {
            // 将模板引擎渲染的内容写入响应:
            PrintWriter pw = resp.getWriter();
            this.viewEngine.render(modelAndView, pw);
            pw.flush();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        String path = req.getRequestURI().substring(req.getContextPath().length());
        PostDispatcher postDispatcher = this.postMappings.get(path);
        if (postDispatcher == null) {
            resp.sendError(404);
            return;
        }
        // 调用Controller方法获得返回值:
        ModelAndView modelAndView;
        try {
            modelAndView = postDispatcher.invoke(req, resp);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        // 允许返回null:
        if (modelAndView == null) {
            return;
        }

        // 处理重定向
        String view = modelAndView.getView();
        if (view.startsWith("redirect:")) {
            resp.sendRedirect(req.getContextPath() + view.substring("redirect:".length()));
        } else {
            // 将模板引擎渲染的内容写入响应:
            PrintWriter pw = resp.getWriter();
            this.viewEngine.render(modelAndView, pw);
            pw.flush();
        }
    }
}
